/* EasyWay Game Engine
 * Copyright (C) 2006 Daniele Paggi.
 *  
 * Written by: 2006 Daniele Paggi<dshnt@hotmail.com>
 *   
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Library General Public License for more details.
 * 
 * You should have received a copy of the GNU Library General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package org.easyway.objects.texture;

import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;

import org.easyway.interfaces.base.IDestroyable;
import org.easyway.interfaces.base.ITexture;
import org.easyway.objects.sprites2D.Mask;
import org.easyway.system.state.OpenGLState;
import org.easyway.utils.ImageUtils;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.glu.GLU;

/**
 * With this class you can use images in the game.<br>
 * <br>
 * example:<br>
 * ...<br>
 * Texture img = new Texture( "path" );<br>
 * new Sprite( img );<br>
 * ...<br>
 */
public class TextureCompact implements IDestroyable, ITexture, Serializable {

	/** version */
	private static final long serialVersionUID = 7592587215114269846L;

	/** indicates if the texture is destroyed or not */
	private boolean destroyed = false;

	/**
	 * logical size of texture
	 * 
	 * @see #widthHW
	 * @see #heightHW
	 */
	public int width, height;

	/**
	 * fisical (hardware) size of texture<br>
	 * the HW size is a power of 2 value (2,4,8,16,32,64,128...)<br>
	 * 
	 * @see #width
	 * @see #height
	 */
	public int widthHW, heightHW;

	/**
	 * internal texture coordinates<br>
	 * it's used to get the max border of texture...
	 * 
	 * @see #xEnd
	 * @see #yEnd
	 */
	public float xStart = 0, yStart = 0;

	/**
	 * internal texture coordinates<br>
	 * it's used to get the max border of texture...
	 * 
	 * @see #xStart
	 * @see #yStart
	 */
	public float xEnd = 1, yEnd = 1;

	/**
	 * indicates if the width and height are equal to widthHW and heightHW.
	 */
	public boolean isRegular;

	/** the ID of texture */
	public int texture;

	/** the mask used to collision */
	protected Mask mask;

	/**
	 * this texture is a black\white texture used in the 2D graphic to creates
	 * trasparents sections
	 */
	public ITexture alphaMask;

	/** id of texture */
	public IntBuffer textureBuffer;

	/** indicates if the texture has trasparent section(not solid) or not(solid) */
	public boolean isSolid;

	/** name of texure */
	public String name = "$_TEXTURE";

	// ------------------------------------------------------

	/**
	 * LOADS and crete a texture from a file<br>
	 * 
	 * @param file
	 *            relative path
	 */
	public TextureCompact(String file) {
		this(ImageUtils.loadTexture(file, false, (byte) 0, (byte) 0, (byte) 0,
			false));
		name = file;
	}

	/**
	 * create a new empty texture.
	 * 
	 * @param width
	 *            size of texture
	 * @param height
	 *            size of texture
	 * @param isToAdd
	 *            indicates if the texture is to add or not to the game engine's
	 *            list
	 */
	public TextureCompact(int width, int height) {
		// if (isToAdd)
		// StaticRef.textures.add(this);
		TextureCompact t = ImageUtils.makeTexture(width, height);
		this.width = t.width;
		this.height = t.height;
		this.widthHW = t.widthHW;
		this.heightHW = t.heightHW;
		this.xEnd = t.xEnd;
		this.yEnd = t.yEnd;
		this.texture = t.texture;
		this.textureBuffer = t.textureBuffer;
		this.isRegular = t.isRegular;
		this.name = t.name;
		this.alphaMask = t.alphaMask;
		name = "$_TEXTURE";
	}

	/**
	 * loads and create a texture from a file and cretes as trasparent the
	 * section that have the color equal to the RGB valued spefied
	 * 
	 * @param file
	 *            relative path
	 * @param red
	 *            red value to compare
	 * @param green
	 *            green value to compare
	 * @param blue
	 *            blue value to compare
	 */
	public TextureCompact(String file, int red, int green, int blue) {
		this(ImageUtils.loadTexture(file, (byte) red, (byte) green,
			(byte) blue, true));
		name = file;
	}

	/**
	 * LOADS and crete a texture from a file<br>
	 * 
	 * @param file
	 *            relative path
	 * @param textureName
	 *            name of texture
	 * 
	 */
	public TextureCompact(String file, String textureName) {
		this(ImageUtils.loadTexture(file));
		name = textureName;
	}

	/**
	 * loads and create a texture from a file and cretes as trasparent the
	 * section that have the color equal to the RGB valued spefied
	 * 
	 * @param file
	 *            relative path
	 * @param red
	 *            red value to compare
	 * @param green
	 *            green value to compare
	 * @param blue
	 *            blue value to compare
	 * @param textureName
	 *            name of texture
	 */
	public TextureCompact(String file, int red, int green, int blue,
			String textureName) {
		this(ImageUtils.loadTexture(file, true, (byte) red, (byte) green,
			(byte) blue, true));
		name = textureName;
	}

	/**
	 * creates a copy of an existent texture<br>
	 * 
	 * @param t
	 *            original texture
	 */

	public TextureCompact(TextureCompact t) {
		// if (toAdd)
		// StaticRef.textures.add(this);
		this.width = t.width;
		this.height = t.height;
		this.widthHW = t.widthHW;
		this.heightHW = t.heightHW;
		this.xEnd = t.xEnd;
		this.yEnd = t.yEnd;
		this.texture = t.texture;
		this.textureBuffer = t.textureBuffer;
		this.isRegular = t.isRegular;
		this.name = t.name;
		this.alphaMask = t.alphaMask;
		// calculateUV();
	}

	/**
	 * creates an emptiy texture<br>
	 */
	public TextureCompact() {
	}

	// ------------------------------------------------------

	// ------------------------------------------------------
	// -----------------------DATA_MANAGMENT-----------------
	// ------------------------------------------------------

	public ByteBuffer getData() {
		bind();
		// GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture);
		ByteBuffer bb = ByteBuffer.allocateDirect(heightHW * widthHW * 4);
		GL11.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA,
			GL11.GL_UNSIGNED_BYTE, bb);
		return bb;
	}

	public void setData(ByteBuffer data) {
		if (data == null)
			return;
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture);
		data.rewind();
		// old:
		// GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, widthHW,
		// heightHW, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, data);
		// new:
		GL11.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, widthHW, heightHW,
			GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, data);
	}

	/**
	 * depure the image from some usually errors that are presents if "broken"
	 * gif images.<br>
	 * 
	 * @param limit
	 *            a factor of depuration
	 * @return new depured Texture
	 */
	public TextureCompact depure(float limit) {
		ImageUtils.depureTexture(this, limit);
		return this;
	}

	// ------------------------------------------------------
	// ------------------------USERS-------------------------
	// ------------------------------------------------------

	/**
	 * make the RGB specified transparent<br>
	 * 
	 */
	public void makeTransp(byte red, byte green, byte blue) {
		ByteBuffer bb = getData();
		bb.rewind();

		byte r, g, b;
		int position;
		int step = widthHW * 4; // lenght of a line
		for (int y = 0; y < heightHW; ++y) {
			position = bb.position();

			for (int x = 0; x < widthHW; ++x) {
				r = bb.get(); // red
				g = bb.get(); // green
				b = bb.get(); // blue
				if (r == red && g == green && b == blue) { // test
					bb.put((byte) 0); // transparent
				} else {
					bb.get(); // ignore alpha
				}
				if (x == width) { // out image?
					bb.position(position + step);
					break; // x = widthHW
				}
			}
			if (y == height) {
				break; // y = heightHW // end
			}
		}

		setData(bb);
		isSolid = false;
	}

	/**
	 * recalculates the u,v fields.<br>
	 * 
	 */
	public void calculateUV() {
		xEnd = (float) width / widthHW;
		yEnd = (float) height / heightHW;
	}

	// ------------------------------------------------------
	// ----------------------COLL_MASK-----------------------
	// ------------------------------------------------------

	/**
	 * indicates if the texture has a mask of collisions or not
	 */
	public boolean isCollisionable() {
		if (mask != null)
			return true;
		return false;
	}

	/**
	 * returns the mask of collisions
	 * 
	 * @return mask of collisions
	 */
	public Mask getMask() {
		return mask;
	}

	/**
	 * sets the mask of collisions
	 * 
	 * @param mask
	 *            the mask of collisions to set
	 */
	public void setMask(Mask mask) {
		this.mask = mask;
	}

	// ------------------------------------------------------
	// -----------------------ALPHA-MASK---------------------
	// ------------------------------------------------------
	/**
	 * sets the black\white texture to use to creates the transparent effect on
	 * 2D scenes
	 */
	public void setAlphaMask(ITexture texture) {
		this.alphaMask = texture;
	}

	/**
	 * gets the black\white texture to use to creates the transparent effect on
	 * 2D scenes
	 */
	public ITexture getAlphaMask() {
		return alphaMask;
	}

	// ------------------------------------------------------
	// ---------------------DESTROY--------------------------
	// ------------------------------------------------------

	/**
	 * destroys the image and release all resources
	 */
	public void destroy() {
		if (destroyed)
			return;
		this.bind();
		// TODO: QUI il "onDestroy?" - rimosso
		destroyed = true;
		if (textureBuffer != null) {
			textureBuffer.rewind();
			GL11.glDeleteTextures(textureBuffer);
		}
		textureBuffer = null;
		// StaticRef.textures.remove(this);
		OpenGLState.lastTextureID = -1;
	}

	public void finalize() {
		if (!destroyed)
			destroy();
	}

	/**
	 * returns if the texture is destroyed or not
	 * 
	 */
	public boolean isDestroyed() {
		return destroyed;
	}

	// ------------------------------------------------------
	// ---------------------SERIALIZATION--------------------
	// ------------------------------------------------------

	private void writeObject(java.io.ObjectOutputStream s)
			throws java.io.IOException {
		// the comments: "XXX" are information not necessary..
		s.writeObject(name);
		s.writeInt(width);
		s.writeInt(height);
		s.writeInt(widthHW);
		s.writeInt(heightHW);
		s.writeFloat(xEnd);// XXX
		s.writeFloat(yEnd);// XXX
		s.writeBoolean(isRegular);
		s.writeBoolean(isSolid);
		s.writeObject(mask);
		s.writeObject(alphaMask);
		s.writeInt(texture);// XXX
		textureBuffer.rewind();
//		for (int i = 0; i < 4; i++) {
//			s.writeInt(textureBuffer.get());// XXX
//		}
		ByteBuffer bb = getData();
		bb.rewind();

		for (int i = 0; i < widthHW * heightHW * 4; i++) {
			s.writeByte(bb.get());
		}
	}

	private void readObject(java.io.ObjectInputStream s)
			throws java.io.IOException, ClassNotFoundException {
		name = (String) s.readObject();
		width = s.readInt();
		height = s.readInt();
		widthHW = s.readInt();
		heightHW = s.readInt();
		xEnd = s.readFloat();// XXX
		yEnd = s.readFloat();// XXX
		calculateUV();
		isRegular = s.readBoolean();
		isSolid = s.readBoolean();
		mask = (Mask) s.readObject();
		alphaMask = (TextureCompact) s.readObject();
		texture = s.readInt(); // XXX

		IntBuffer buf = BufferUtils.createIntBuffer(4);
		// buf.rewind();
//		for (int i = 0; i < 4; i++) {
//			// buf.put(s.readInt());
//			s.readInt(); // XXX
//		}
		// buf.rewind();
		// textureBuffer = buf;

		GL11.glGenTextures(buf);
		textureBuffer = buf;
		texture = buf.get(0);
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture);

		ByteBuffer bb = BufferUtils.createByteBuffer(widthHW * heightHW * 4);
		bb.rewind();

		for (int i = 0; i < widthHW * heightHW * 4; i++) {
			bb.put(s.readByte());
		}

		bb.rewind();
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER,
			GL11.GL_LINEAR_MIPMAP_LINEAR);
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER,
			GL11.GL_LINEAR);
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S,
			GL12.GL_CLAMP_TO_EDGE);
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T,
			GL12.GL_CLAMP_TO_EDGE);
		GLU.gluBuild2DMipmaps(GL11.GL_TEXTURE_2D, GL11.GL_RGBA, widthHW,
			heightHW, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, bb);
		// GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, widthHW,
		// heightHW, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, bb);
		OpenGLState.lastTextureID = -1;
	}

	// ------------------------------------------------------
	// ------------------------------------------------------

	public int getHeight() {
		return height;
	}

	public int getHeightHW() {
		return heightHW;
	}

	public int getID() {
		return texture;
	}

	public int getWidth() {
		return width;
	}

	public int getWidthHW() {
		return widthHW;
	}

	public float getXEnd() {
		return xEnd;
	}

	public float getXStart() {
		return xStart;
	}

	public float getYEnd() {
		return yEnd;
	}

	public float getYStart() {
		return yStart;
	}

	/**
	 * Blinds the image
	 */
	public void bind() {
		if (OpenGLState.lastTextureID != texture)
			if (texture > 0) {
				OpenGLState.lastTextureID = texture;
				if (isSolid /* || alphaMask == null */) {
					OpenGLState.disableAlphaTest();
				} else {
					OpenGLState.enableAlphaTest();
				}
				GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture);
			} else
				org.easyway.utils.Utility.error("texture<=0", "Texture.bind()");
	}

	public String toString() {
		return name;
	}

}
